package com.tiancheng.trade.payment.channel.ums.utils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 银联商务工具类
 */
@Slf4j
public final class UmsUtil {
    /**
     * 默认的密码字符串组合，用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    /**
     * 证书类型，值为{@value}
     */
    public static final String CERT_TYPE_PKCS12 = "PKCS12";
    /**
     * 签名证书
     */
    private static KeyStore keyStore = null;
    /**
     * 签名验签算法，值为{@value}
     */
    public final static String ALGORITHM_SHA256WITHRSA = "SHA256withRSA";
    /**
     * 证书类型，值为{@value}
     */
    public static final String CERT_TYPE_JKS = "JKS";
    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            log.error("{}初始化失败: {}", UmsUtil.class.getName(), e);
        }
    }


    /**
     * 构建签名字符串
     * 除sign外所有参数名按照ASCII字典序排序，参数名及对应参数值以key=value形式拼接，参数间使用”&”符号连接，
     * 并拼接上通讯密钥key作为待签名字符串，最后使用SHA256签名算法计算待签名字符串的签名结果即为签名sign的值
     *
     * @param md5Key String
     * @param object Object 只能传对象，不能是集合、列表
     * @return String
     */
    public static String buildSignString(String md5Key, Object object) {
        if (object == null) return "";
        TreeMap<String, String> map = new TreeMap();
        for (Field field : object.getClass().getDeclaredFields()) {
            try {
                field.setAccessible(true);
                // 属性值为null或空串，不收集
                if (null == field.get(object) || StringUtils.isBlank(field.get(object).toString())) continue;
                // 如果属性是集合或者项目中定义的对象，转为json格式字符串
                if (field.get(object) instanceof Collection
                        || field.get(object) instanceof Map
                        || field.get(object).getClass().getName().startsWith("com.tiancheng.trade.payment")) {
                    map.put(field.getName(), JSONObject.toJSONString(field.get(object)));
                } else {
                    map.put(field.getName(), String.valueOf(field.get(object)));
                }
            } catch (IllegalAccessException e) {
                log.error("构建签名字符串异常: errorMsg={}", e.getMessage());
                return null;
            }
        }
        return handleSignString(map, md5Key);
    }

    /**
     * 构建签名字符串
     * 除sign外所有参数名按照ASCII字典序排序，参数名及对应参数值以key=value形式拼接，参数间使用”&”符号连接，
     * 并拼接上通讯密钥key作为待签名字符串，最后使用SHA256签名算法计算待签名字符串的签名结果即为签名sign的值
     *
     * @param md5Key String
     * @param object Object 只能传对象，不能是集合、列表
     * @return String
     */
    public static String dhjtBuildSignString(String md5Key, Object object) {
        if (object == null) return "";
        TreeMap<String, String> map = new TreeMap();
        for (Field field : object.getClass().getDeclaredFields()) {
            try {
                field.setAccessible(true);
                // 属性值为null或空串，不收集
                if (null == field.get(object) || StringUtils.isBlank(field.get(object).toString())) continue;
                // 如果属性是集合或者项目中定义的对象，转为json格式字符串
                if(field.get(object) instanceof Collection){
                    map.put(field.getName(), JSONArray.toJSONString(field.get(object), JSONWriter.Feature.WriteMapNullValue));
                } else if (field.get(object) instanceof Map
                        || field.get(object).getClass().getName().startsWith("com.tiancheng.trade.payment")) {
                    map.put(field.getName(), JSON.toJSONString(field.get(object), JSONWriter.Feature.WriteMapNullValue));
                } else {
                    map.put(field.getName(), String.valueOf(field.get(object)));
                }
            } catch (IllegalAccessException e) {
                log.error("构建签名字符串异常: errorMsg={}", e.getMessage());
                return null;
            }
        }
        return dhjtHandleSignString(map, md5Key);
    }

    /**
     * 构建签名字符串
     * 除sign外所有参数名按照ASCII字典序排序，参数名及对应参数值以key=value形式拼接，参数间使用”&”符号连接，
     * 并拼接上通讯密钥key作为待签名字符串，最后使用SHA256签名算法计算待签名字符串的签名结果即为签名sign的值
     *
     * @param md5Key String
     * @param object Object 只能传对象，不能是集合、列表
     * @return String
     */
    public static String dhjtBuildSignStringNew(String md5Key, Object object) {
        if (object == null) return "";
        SortedMap<String, Object> sortedMap = JsonUtil.fromJson(JsonUtil.toJsonString(object), TreeMap.class);
        try {
            StringBuilder sb = generateSignPlain(sortedMap);
            return DigestUtils.sha256Hex(sb.append(md5Key).toString()).toUpperCase();
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 按照key值字典顺序，并用&拼接
     *
     * @param params
     * @return
     */
    private static StringBuilder generateSignPlain(SortedMap<String, Object> params) throws UnsupportedEncodingException {
        StringBuilder plain = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() instanceof List) {
                List<Object> list = (List<Object>) entry.getValue();
                if (list != null && list.size() > 0) {
                    plain.append(entry.getKey() + "=");
                    if (list.get(0) instanceof String) {
                        for (int i = 0; i < list.size(); i++) {
                            plain.append(i).append("=").append(list.get(i).toString()).append("&");
                        }
                    } else {
                        for (int i = 0; i < list.size(); i++) {
                            plain.append(generateSignPlain(new TreeMap<>((Map)list.get(i))));
                            plain.append("&");
                        }
                    }
                }
            } else if (entry.getValue() instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) entry.getValue();
                if (map != null && map.size() > 0) {
                    plain.append(entry.getKey() + "=").append(generateSignPlain(new TreeMap<>(map))).append("&");
                }
            } else {
                if("sign".equals(entry.getKey())) {
                    continue;
                }
                if(entry.getValue() != null && StringUtils.isNotEmpty(entry.getValue().toString())) {
                    String encodeStr = URLEncoder.encode(String.valueOf(entry.getValue()), "UTF-8");
                    plain.append(entry.getKey() + "=" + encodeStr.replaceAll("\\+", "%20").replaceAll("\\*", "%2A") + "&");
                }
            }
        }
        return plain.length() > 0 ? plain.deleteCharAt(plain.length() - 1) : new StringBuilder();
    }

    /**
     * 构建签名字符串
     * 除sign外所有参数名按照ASCII字典序排序，参数名及对应参数值以key=value形式拼接，参数间使用”&”符号连接，
     * 并拼接上通讯密钥key作为待签名字符串，最后使用SHA256签名算法计算待签名字符串的签名结果即为签名sign的值
     *
     * @param md5Key String
     * @param params Object 只能传Map
     * @return String
     */
    public static Optional<String> buildSignString2(String md5Key, Map<String, Object> params) {
        try {
            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() instanceof Map || entry.getValue() instanceof List) {
                    map.put(entry.getKey(), JsonUtil.toJsonString(entry.getValue()));
                } else {
                    map.put(entry.getKey(), String.valueOf(entry.getValue()));
                }
            }
            return Optional.of(handleSignString(map, md5Key));
        } catch (Exception e) {
            log.error(e.getMessage());
            return Optional.empty();
        }
    }


    /**
     * 过滤空值、sign属性、ASCII字典序排序、构建签名字符串
     *
     * @param map Map
     * @return String
     */
    public static String dhjtHandleSignString(Map<String, String> map, String md5Key) {
        List<String> keys = map.keySet().stream().filter(d -> (!"sign".equals(d) && null != map.get(d))).sorted().collect(Collectors.toList());
        StringBuilder buf = new StringBuilder();
        for (String key : keys) {
            buf.append(key).append("=").append(map.get(key)).append("&");
        }
        StringBuffer sb = new StringBuffer();// 代签名的字符串
        StringBuffer req = new StringBuffer();// 组织请求参数
        try {
            for (String key : map.keySet()) {
                if (map.get(key) != null && !"".equals(map.get(key))) {
                    if (sb.length() == 0) {
                        sb.append(key + "=" + map.get(key));
                        req.append(key + "=" + URLEncoder.encode(map.get(key), "UTF-8"));
                    } else {
                        sb.append("&" + key + "=" + map.get(key));
                        req.append("&" + key + "=" + URLEncoder.encode(map.get(key), "UTF-8"));
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        log.info("待签名的字符串是：{}", sb.toString());
        log.info("请求参数为：{}", req.toString());
        sb.append(md5Key);
        return DigestUtils.sha256Hex(sb.toString()).toUpperCase();
    }

    /**
     * 过滤空值、sign属性、ASCII字典序排序、构建签名字符串
     *
     * @param map Map
     * @return String
     */
    public static String handleSignString(Map<String, String> map, String md5Key) {
        List<String> keys = map.keySet().stream().filter(d -> (!"sign".equals(d) && null != map.get(d))).sorted().collect(Collectors.toList());
        StringBuilder buf = new StringBuilder();
        for (String key : keys) {
            buf.append(key).append("=").append(map.get(key)).append("&");
        }
        String text = StringUtils.isEmpty(md5Key) ?
                buf.substring(0, buf.length() - 1) : buf.substring(0, buf.length() - 1) + md5Key;
        return DigestUtils.sha256Hex(text.getBytes(StandardCharsets.UTF_8)).toUpperCase();
    }


    /**
     * 生成文件的md5校验值
     *
     * @param file File
     * @return String
     * @throws IOException
     */
    public static String getFileMD5String(File file) throws IOException {
        InputStream fis = new FileInputStream(file);
        byte[] buffer = new byte[1024];
        int numRead;
        while ((numRead = fis.read(buffer)) > 0) {
            messagedigest.update(buffer, 0, numRead);
        }
        fis.close();
        return bufferToHex(messagedigest.digest());
    }

    /**
     * 生成文件的md5校验值
     *
     * @param fis InputStream
     * @return String
     * @throws IOException
     */
    public static String getFileInputStreamMD5String(InputStream fis) throws IOException {
        byte[] buffer = new byte[1024];
        int numRead;
        while ((numRead = fis.read(buffer)) > 0) {
            messagedigest.update(buffer, 0, numRead);
        }
        return bufferToHex(messagedigest.digest());
    }

    public static ByteArrayOutputStream cloneInputStream(InputStream input) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) > -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String bufferToHex(byte[] bytes) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte[] bytes, int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        for (int l = m; l < (m + n); l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        stringbuffer.append(hexDigits[(bt & 0xf0) >> 4]);// 取字节中高 4 位的数字转换, >>> 为逻辑右移，将符号位一起右移,此处未发现两种符号有何不同
        stringbuffer.append(hexDigits[bt & 0xf]);// 取字节中低 4 位的数字转换
    }

    /**
     * 从私钥证书中获取PKCS12标准私钥
     *
     * @param keyFileIn 私钥文件流 .pfx
     * @param keypwd    证书密码
     * @return 私钥
     */
    public static PrivateKey getPriKeyPkcs12(InputStream keyFileIn, String keypwd) {
        return getPriKey(keyFileIn, keypwd, CERT_TYPE_PKCS12);
    }

    /**
     * 从私钥证书中获取私钥
     *
     * @param keyFileIn 证书文件流.pfx, .keystore)
     * @param keypwd    证书密码
     * @param type      证书类型
     * @return 私钥
     */
    public static PrivateKey getPriKey(InputStream keyFileIn, String keypwd, String type) {
        PrivateKey privateKey = null;
        getKeyStore(keyFileIn, keypwd, type);
        try {
            Enumeration<String> aliasenum = keyStore.aliases();
            String keyAlias = null;
            if (aliasenum.hasMoreElements()) {
                // 第一个条目
                keyAlias = aliasenum.nextElement();
            }
            privateKey = (PrivateKey) keyStore.getKey(keyAlias, keypwd.toCharArray());
        } catch (Exception e) {
            log.error("Fail: get private key from private certificate", e);
        }
        return privateKey;
    }

    /**
     * 将证书文件读取为证书存储对象：证书文件类型可为：JKS（.keystore等），PKCS12（.pfx）
     *
     * @param keyFileIn 证书文件文件流
     * @param keypwd    证书密码
     * @param type      证书类型
     * @return 证书对象
     */
    public static KeyStore getKeyStore(InputStream keyFileIn, String keypwd, String type) {
        try {
            if (CERT_TYPE_JKS.equals(type)) {
                keyStore = KeyStore.getInstance(type);
            } else if (CERT_TYPE_PKCS12.equals(type)) {
                /*
                 * 动态注册SUN JCE
                 * JCE（Java Cryptography Extension）是一组包，它们提供用于加密、密钥生成和协商以及 Message
                 * Authentication Code（MAC）算法的框架和实现。
                 * 设置安全提供者为BouncyCastleProvider
                 */
                if (null != Security.getProvider("BC")) {
                    Security.removeProvider("BC");
                }
                Security.addProvider(new BouncyCastleProvider());
                keyStore = KeyStore.getInstance(type);
            }
            char[] nPassword = null;
            nPassword = (null == keypwd || "".equals(keypwd.trim())) ? null : keypwd.toCharArray();
            keyStore.load(keyFileIn, nPassword);
            if (keyFileIn != null)
                keyFileIn.close();
            return keyStore;
        } catch (Exception e) {
            if (Security.getProvider("BC") == null) {
                log.info("BC Provider not installed.");
            }
            log.error("Fail: load privateKey certificate", e);
        } finally {
            if (keyFileIn != null)
                try {
                    keyFileIn.close();
                } catch (IOException ignored) {
                }
        }
        return null;
    }

    /**
     * 银联上传文件签名
     *
     * @param rawData
     * @param privateKey
     * @param algorithm
     * @return
     */
    public static byte[] sign(byte[] rawData, PrivateKey privateKey, String algorithm) {
        try {
            Signature e = Signature.getInstance(algorithm);
            e.initSign(privateKey);
            e.update(rawData);
            return e.sign();
        } catch (Exception e) {
            log.error("银联上传文件签名失败");
            return null;
        }
    }

    /**
     * 字节数组转换为十六进制字符串
     *
     * @param byteArr 字节数组
     * @return 十六进制字符串
     */
    public static String byteArr2HexString(byte[] byteArr) {
        if (byteArr == null) return "null";
        StringBuilder sb = new StringBuilder();
        for (byte b : byteArr) {
            if ((b & 0xFF) < 16) sb.append("0");
            sb.append(Integer.toString(b & 0xFF, 16));
        }
        return sb.toString();
    }

    public static byte[] hexString2ByteArr(String hexstring) {
        if (hexstring != null && hexstring.length() % 2 == 0) {
            byte[] dest = new byte[hexstring.length() / 2];

            for (int i = 0; i < dest.length; ++i) {
                String val = hexstring.substring(2 * i, 2 * i + 2);
                dest[i] = (byte) Integer.parseInt(val, 16);
            }

            return dest;
        } else {
            return new byte[0];
        }
    }

    public static Boolean verify(byte[] rawData, byte[] signature, PublicKey publicKey) {
        try {
            Signature e = Signature.getInstance(ALGORITHM_SHA256WITHRSA);
            e.initVerify(publicKey);
            e.update(rawData);
            return e.verify(signature);
        } catch (Exception e) {
            log.error("验签失败: {}", e.getMessage());
            return null;
        }
    }
}
